import * as THREE from "three";

// 引入轨道控制器控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 引入动画库
import gsap from "gsap";
// 引入数据图形用户界面库
import * as dat from "dat.gui";

console.log(THREE);

//three.js 基本内容

// 1.创建场景  场景能够让你在什么地方、摆放什么东西来交给three.js来渲染，这是你放置物体、灯光和摄像机的地方。
const scene = new THREE.Scene();

// 2.创建相机 

//  透视相机（PerspectiveCamera）这一摄像机使用perspective projection（透视投影）来进行投影。
// 这一投影模式被用来模拟人眼所看到的景象，它是3D场景的渲染中使用得最普遍的投影模式。
// PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
// fov — 摄像机视锥体垂直视野角度   // aspect — 摄像机视锥体长宽比  // near — 摄像机视锥体近端面  // far — 摄像机视锥体远端面
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

//2.1设置相机的位置 position表示对象局部位置的Vector3。默认值为(0, 0, 0)。

// Vector3( x : Float, y : Float, z : Float )   该类表示的是一个三维向量（3D vector）。'
//  一个三维向量表示的是一个有顺序的、三个为一组的数字组合（标记为x、y和z）， 可被用来表示很多事物:
//  一个位于三维空间中的点。
// 一个在三维空间中的方向与长度的定义。在three.js中，长度总是从(0, 0, 0)到(x, y, z)的 Euclidean distance（欧几里德距离，即直线距离）， 
// 方向也是从(0, 0, 0)到(x, y, z)的方向。
// .set ( x : Float, y : Float, z : Float ) : this
// 设置该向量的x、y 和 z 分量。


camera.position.set(0, 0, 10);
scene.add(camera); //添加相机到场景中

// 3.创建物体 其他种类的几何体    
const Geometry = new THREE.BoxGeometry(2, 2, 2, 100, 100, 100);


// 设置Texture.load（单文件） 与 加载管理器 中的事件
// .load ( url : String, onLoad : Function, onProgress : Function, onError : Function ) : Texture
// url — 文件的URL或者路径，也可以为 Data URI.
// onLoad — 加载完成时将调用。回调参数为将要加载的texture.
// onProgress — 将在加载过程中进行调用。参数为XMLHttpRequest实例，实例包含total和loaded字节。
// onError — 在加载错误时被调用。
let event = {
    onLoad() {
        console.log("贴图加载完成");
    },
    onProgress(url,itemsLoaded,itemsTotal) {
        console.log("贴图加载中...");
        console.log( 'Loading file: ' + url + '.Loaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' );
        console.log( (itemsLoaded/itemsTotal).toFixed(2) * 100 + "%" );
        document.getElementById("loadInfo").innerText =  (itemsLoaded/itemsTotal).toFixed(2) * 100 + "%" ;
    },
    onError(e) {
        console.log("贴图加载错误", e);
    }
}

// 设置加载管理器
// 其功能是处理并跟踪已加载和待处理的数据。如果未手动设置加强管理器，则会为加载器创建和使用默认全局实例加载器管理器 
// 一般来说，默认的加载管理器已足够使用了，但有时候也需要设置单独的加载器 - 例如，如果你想为对象和纹理显示单独的加载条。
const loadingManager = new THREE.LoadingManager(event.onLoad,event.onProgress,event.onError);


// 导入纹理
// TextureLoader( manager : LoadingManager )
// manager — 加载器使用的loadingManager，默认值为THREE.DefaultLoadingManager.
// 使用自定义加载管理器
const textureloader = new THREE.TextureLoader(loadingManager);
const doorColorTexture = textureloader.load("./textures/door/color.jpg");//材质纹理
const testTexture = textureloader.load("./textures/minecraft.png");
const doorAlphaTexture = textureloader.load("./textures/door/alpha.jpg");//材质透明纹理
const doorAoTexture = textureloader.load("./textures/door/ambientOcclusion.jpg"); //纹理环境遮挡贴图
const doorHieghtTexture = textureloader.load("./textures/door/height.jpg"); //纹理环境位移（高度）贴图
const roughnessTexture = textureloader.load("./textures/door/roughness.jpg")//纹理粗糙度贴图
const matalnessTexture = textureloader.load("./textures/door/metalness.jpg");//纹理金属贴图

//单图片加载事件  单个加载 目前暂不支持onProgress的回调
const normalTexture = textureloader.load("./textures/door/normal.jpg"
    // ，event.onLoad, event.onProgress, event.onError
);//纹理法线贴图


// 使用 标准网格材质(MeshStandardMaterial)
// 一种基于物理的标准材质，使用Metallic-Roughness工作流程。
// 基于物理的渲染（PBR）最近已成为许多3D应用程序的标准，例如Unity， Unreal和 3D Studio Max。
// 这种方法与旧方法的不同之处在于，不使用近似值来表示光与表面的相互作用，而是使用物理上正确的模型。
//  我们的想法是，不是在特定照明下调整材质以使其看起来很好，而是可以创建一种材质，能够“正确”地应对所有光照场景。
// 在实践中，该材质提供了比MeshLambertMaterial 或MeshPhongMaterial 更精确和逼真的结果，代价是计算成本更高。
const cubeMaterial = new THREE.MeshStandardMaterial({
    // color: "#bdc81d",
    // 设置材质纹理
    map: doorColorTexture,
    // 设置材质透明纹理 alpha贴图是一张灰度纹理，用于控制整个表面的不透明度。（黑色：完全透明；白色：完全不透明）
    alphaMap: doorAlphaTexture,
    transparent: true,
    // 设置纹理环境遮挡贴图与强度
    // .aoMap : Texture   该纹理的红色通道用作环境遮挡贴图。默认值为null。aoMap需要第二组UV。
    // .aoMapIntensity : Float  环境遮挡效果的强度。默认值为1。零是不遮挡效果。
    aoMap: doorAoTexture,
    aoMapIntensity: 2,
    // 设置纹理环境位移贴图与强度
    // .displacementMap : Texture
    // 位移贴图会影响网格顶点的位置，与仅影响材质的光照和阴影的其他贴图不同，移位的顶点可以投射阴影，阻挡其他对象， 以及充当真实的几何体。
    // 位移纹理是指：网格的所有顶点被映射为图像中每个像素的值（白色是最高的），并且被重定位。
    // .displacementScale : Float
    // 位移贴图对网格的影响程度（黑色是无位移，白色是最大位移）。如果没有设置位移贴图，则不会应用此值。默认值为1。
    displacementMap: doorHieghtTexture,
    displacementScale: 0.23,
    // 设置纹理环境粗糙度贴图 与 粗糙强度
    // .roughnessMap : Texture
    // 该纹理的绿色通道用于改变材质的粗糙度。 
    // .roughness : Float
    // 材质的粗糙程度。0.0表示平滑的镜面反射，1.0表示完全漫反射。默认值为1.0。如果还提供roughnessMap，则两个值相乘。
    roughnessMap: roughnessTexture,
    roughness: 1.23,
    // 设置纹理金属相似度贴图 与金属相似度强度
    // .metalnessMap : Texture
    // 该纹理的蓝色通道用于改变材质的金属度。
    // .metalness : Float
    // 材质与金属的相似度。非金属材质，如木材或石材，使用0.0，金属使用1.0，通常没有中间值。 
    // 默认值为0.0。0.0到1.0之间的值可用于生锈金属的外观。如果还提供了metalnessMap，则两个值相乘。
    metalnessMap: matalnessTexture,
    metalness: 1,
    // 设置材质纹理法线贴图
    // .normalMap : Texture
    // 用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线，并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状，只会改变光照。
    normalMap: normalTexture
});

// 3.3根据几何体与材质创建物体
const cube = new THREE.Mesh(Geometry, cubeMaterial);


// 设置纹理环境位移贴图与强度 需要分段数 几何体的分段数越高,顶点越多,模型精度越高
const planeGeometry = new THREE.PlaneGeometry(2, 2, 100, 100);
const cube1 = new THREE.Mesh(planeGeometry, cubeMaterial);
cube1.material.side = THREE.DoubleSide
cube1.position.x = 3
//给物体设置第二组uv
Geometry.setAttribute('uv2', new THREE.BufferAttribute(Geometry.attributes.uv.array, 2));
planeGeometry.setAttribute('uv2', new THREE.BufferAttribute(planeGeometry.attributes.uv.array, 2));


scene.add(cube); //将物体添加到场景中
scene.add(cube1); //将物体添加到场景中


// 添加灯光
// 添加环境光
// AmbientLight 环境光会均匀的照亮场景中的所有物体。环境光不能用来投射阴影，因为它没有方向。
// AmbientLight( color : Color, intensity : Float )
// color -（可选）一个表示颜色的 Color 的实例、字符串或数字，默认为一个白色（0xffffff）的 Color 对象。
// intensity -（可选）光照的强度。默认值为 1。
const light = new THREE.AmbientLight(0x404040, 0.8); // 柔和的白光

// 添加平行光
// 平行光（DirectionalLight）
// 平行光是沿着特定方向发射的光。这种光的表现像是无限远，从它发出的光线都是平行的。常常用平行光来模拟太阳光的效果。 
// 太阳足够远，因此我们可以认为太阳的位置是无限远，所以我们认为从太阳发出的光线也都是平行的。
// DirectionalLight( color : Color, intensity : Float )
// color -（可选）一个表示颜色的 Color 的实例、字符串或数字，默认为一个白色（0xffffff）的 Color 对象。
// intensity -（可选）光照的强度。默认值为 1。 
// .target : Object3D
// 灯光从它的位置（position）指向目标位置。默认的目标位置为(0, 0, 0)。
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
// 设置平行光光源位置
directionalLight.position.set(10, 10, 10);

scene.add(light);
scene.add(directionalLight);


// 4.渲染
// 初始化渲染器
// WebGLRenderer WebGL Render 用WebGL渲染出你精心制作的场景。
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// .domElement : DOMElement   一个canvas，渲染器在其上绘制输出。
// 渲染器的构造函数会自动创建(如果没有传入canvas参数);你需要做的仅仅是像下面这样将它加页面里去:
document.body.appendChild(renderer.domElement); //将绘制canvas添加到页面里



//创建轨道控制器
// OrbitControls( object : Camera, domElement : HTMLDOMElement )
// object: （必须）将要被控制的相机。该相机不允许是其他任何对象的子级，除非该对象是场景自身。
// domElement: 用于事件监听的HTML元素。
const controls = new OrbitControls(camera, renderer.domElement);
// 将其设置为true以启用阻尼（惯性），这将给控制器带来重量感。默认值为false。
// 请注意，如果该值被启用，你将必须在你的动画循环里调用.update()。
controls.enableDamping = true;

// 创建坐标轴辅助器
// AxesHelper  用于简单模拟3个坐标轴的对象.
// 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
// AxesHelper( size : Number )  size -- (可选的) 表示代表轴的线段长度. 默认为 1.
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);


function render() {
    controls.update();
    renderer.render(scene, camera);
    // requestAnimationFrame 是一个用于优化浏览器动画效果的 API。它可以让浏览器在下一次重绘前执行指定的回调函数，
    // 从而可以更加流畅地执行动画效果，避免了使用 setTimeout 或 setInterval 可能引起的性能问题。
    requestAnimationFrame(render);
}

render()

// 窗口变化时，更新渲染画面
window.addEventListener("resize", () => {

    // 更新摄像机视锥体长宽比
    camera.aspect = window.innerWidth / window.innerHeight;
    // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用
    camera.updateProjectionMatrix();

    // 更新渲染器
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 设置渲染器像素比 通常用于避免HiDPI设备上绘图模糊
    renderer.setPixelRatio(window.devicePixelRatio);

})


